
#include "MeshWriteProcess.hpp"
#include "datakit.h"
#include <iostream>
#include "tess/tess.h"
#include "common/logger.h"
#include "./utils/FileUtil.h"
#include "./utils/StringUtil.h"

namespace  AMCAX::Exchange {

    MeshWriteProcess::MeshWriteProcess(Dtk_string inputFile, std::unique_ptr<WriterBase> writer)
        : WriteProcessBase(inputFile, std::move(writer))
    {
    }
    
    Dtk_ErrorStatus MeshWriteProcess::WriteDocument(Dtk_MainDocPtr inDocument, const std::string& outputFile)
    {
        if (inDocument.IsNULL())
            return dtkErrorNullPointer;

        // First we get the root component in the document
        Dtk_ComponentPtr rootComponent = inDocument->RootComponent();
        if (rootComponent.IsNULL())
        {
            return dtkErrorNullPointer;
        }
        // Fix the file type if needed.
        std::string outputFilePath(outputFile);
        auto extension = StringUtil::ToLowerCase(FileUtil::GetExtension(outputFile));

        //if no Error we write the Component
        if (m_fileWriter)
        {
            auto err = m_fileWriter->WriteInit(m_inputFile, outputFilePath.c_str());
            if (err != dtkNoError)
            {
                LOG_ERROR("Write Init with error {}", (int)err);
                return err;
            }
        }

        {
            //Go Through the Root Component and children
            const Dtk_transfo &inMatrix = Dtk_transfo();
            WriteComponent(rootComponent, inMatrix);
        }

        if (m_fileWriter)
        {
            auto err = m_fileWriter->WriteEnd();
            if (err != dtkNoError)
            {
                LOG_ERROR("Write End with error {}", (int)err);
                return err;
            }
        }
        return dtkNoError;
    }
    
    Dtk_ErrorStatus MeshWriteProcess::WriteInstance(Dtk_ComponentPtr inComponent)
    {
    
        //Instance represent a prototype with a matrix placement
        Dtk_string ComponentName;
        ComponentName = inComponent->Name();//Component name
        Dtk_ComponentPtr prototype = inComponent->GetChild(0);
        Dtk_transfo matrix = inComponent->TransformationMatrix();
        Dtk_ID childID = inComponent->GetChild(0)->GetID();

        //you have to write matrix and instance the prototype
        // WriteComponent(prototype, Dtk_transfo());
        Dtk_ID outId;
        //WriteComponent(prototype, matrix);
        WritePrototype(prototype, Dtk_transfo(), outId);
    
        if (m_stkAssembly.size() > 0)
        {
            auto lastAssId = m_stkAssembly.back();
            Dtk_ID outIdInst;
			auto status = m_fileWriter->CreateInstance(lastAssId, outId, Dtk_transfo::create(matrix), outIdInst);
        }
    
        if (m_componentProcessed.find(childID) < 0)
        {
            m_componentProcessed.push_back(childID);
            m_mpComponentProcessed.insert({childID, outId});
        }
    
        return dtkNoError;
    }
        
    // void debug_output(Dtk_ComponentPtr inComponent)
    // {
    //     Dtk_string componentName = inComponent->Name();
    //     Dtk_ID id = inComponent->GetID();
    //     std::cout << "WriteComponent : " << componentName.c_str() << ", id : " << id << ", type : " << inComponent->ComponentType() << std::endl;
    // }

    Dtk_ErrorStatus MeshWriteProcess::WriteComponent(Dtk_ComponentPtr inComponent, const Dtk_transfo &inMatrix, bool bCreateFile)
    {
        Dtk_Size_t i;
    
        //Datakit component are given in MM, if original model had other unit you can get it with GetConceptionUnitScale()
        // UnitFactor = 25.4 for inch 
        double unitFactor;
        Dtk_ErrorStatus err;
        err = inComponent->GetConceptionUnitScale(unitFactor);
        
        //GetAttributes
        Dtk_InfoPtr attributes = inComponent->GetInfos();
        if (attributes.IsNotNULL())
        {
            int CompoActivationStatus, CompoBlankedStatus;
            Dtk_RGB CompoColor;
            CompoActivationStatus = attributes->GetActivationFlag(); // if CompoActivationStatus == 0 Component and his children aren't visible
            CompoBlankedStatus = attributes->GetBlankedStatus(); // if CompoBlankedStatus == 1 Component and his children aren't visible
            CompoColor = attributes->GetColor(); // if CompoBlankedStatus == 1 Component and his children are using this color 
        }
    
        if (m_fileWriter)
        {
            m_fileWriter->InitComponent(inComponent);
        }
        //You have 4 types for Component
        Dtk_Component::ComponentTypeEnum type = inComponent->ComponentType();

        // debug_output(inComponent);

        switch (type)
        {
            //Instance represent a prototype with a matrix placement
        case Dtk_Component::InstanceComponentType:
        {
            WriteInstance(inComponent);
            break;
        }
        //Prototype (you have to check if you ever read and write it to don't waste time)
        //you can use the method inComponent->GetID() to get Unique ID for Component
        case Dtk_Component::PrototypeComponentType:
        {
            Dtk_ID outId;
            WritePrototype(inComponent, inMatrix, outId);
            break;
        }
        //Catalog Component represent a choice of several possible configuration 
        //(like scene in catiav5, workspace in catiav4, configuration in solidworks)
        //Default is the first child 
        case Dtk_Component::CatalogComponentType:
        {
            Dtk_string name = inComponent->Name();//Component name
    
            Dtk_Size_t numComp = inComponent->GetNumChildren();
            if (numComp > 0)
            {
                if (m_fileWriter)
                {
                    for (i = 0; i < numComp; i++)
                    {
                        Dtk_ComponentPtr child = inComponent->GetChild(i);
                        auto childName = child->Name();
                        m_fileWriter->InitComponent(child);
                        m_fileWriter->EndComponent(child);
                    }
                }
                Dtk_Int32 defaultindex = inComponent->GetDefaultChildInCatalog(); //Get Default child to use
                Dtk_ComponentPtr defaultchoice = inComponent->GetChild(defaultindex);
                if (defaultchoice.IsNotNULL())
                {
                    WriteComponent(defaultchoice, inMatrix);
                }
            }
            //if you don't want to use default you have to scan all children and choose the one you want to convert (see their name)
            break;
        }
        //Component containing only children 
        case Dtk_Component::VirtualComponentType:
        {
            Dtk_string name;
            Dtk_Size_t NumChildren;
    
            name = inComponent->Name();//Component name
    
            NumChildren = inComponent->GetNumChildren();
            for (i = 0; i < NumChildren; i++)
            {
                Dtk_ComponentPtr child = inComponent->GetChild(i);
                WriteComponent(child, inMatrix);
            }
            break;
    
        }
        }
    
        if (m_fileWriter)
        {
            m_fileWriter->EndComponent(inComponent);
        }
        return dtkNoError;
    }
    
    Dtk_ErrorStatus MeshWriteProcess::WritePrototype(Dtk_ComponentPtr inComponent, const Dtk_transfo &inMatrix, Dtk_ID& outPrototypeId)
    {
        Dtk_ErrorStatus err = dtkNoError;
        Dtk_ID componentID = inComponent->GetID();
        Dtk_Int32 componentIndex;
        componentIndex = m_componentProcessed.find(componentID);
    
        if (componentIndex == -1)
        {
            Dtk_ID outAssemblyId;
            Dtk_API *inAPI = Dtk_API::GetAPI();
            if (inComponent->IsAssembly())
            {
                if (m_fileWriter)
                {
                    err = m_fileWriter->CreateAssembly(inComponent, outAssemblyId);
                    m_stkAssembly.push_back(outAssemblyId);
                }

                //it can also contain some instances
                Dtk_Size_t numChildren = inComponent->GetNumChildren();
                //std::cout << inComponent->Name().c_str() << " has children count: " << (int)numChildren << std::endl;
                for (Dtk_Size_t i = 0; i < numChildren; i++)
                {
                    Dtk_ComponentPtr child = inComponent->GetChild(i);
                    std::cout << inComponent->Name().c_str() << " child " << i << " : " << child->Name().c_str() << std::endl;
                    if (child->ComponentType() != Dtk_Component::InstanceComponentType)
                    {
                        std::cout << "UNEXPECTED ---------------------> why not instance!" << std::endl;
                    }
                }

                for (Dtk_Size_t i = 0; i < numChildren; i++)
                {
                    Dtk_ComponentPtr child = inComponent->GetChild(i);
                    WriteComponent(child, inMatrix);
                }

                outPrototypeId = outAssemblyId;
                m_stkAssembly.pop_back();

                if (m_fileWriter)
                {
                    m_fileWriter->CloseAssembly(inComponent);
                }                
            }
            else
            {

                Dtk_NodePtr rootNode;
    
                //Get the Construction tree for this prototype
                if (inComponent->ComponentAvailability() == Dtk_Component::ComponentMissing)
                    err = dtkErrorFileNotExist;
                else
                    err = inAPI->ReadComponent(inComponent, rootNode);
        
                std::cout << "Read Component " << inComponent->Name().c_str() << std::endl;
                //a RootNode=  NULL with err == dtkNoError means that component is empty
                if (err == dtkNoError && rootNode.IsNotNULL())
                {
                    // Set matrix to apply to all node if you flat assemblies
                    m_currentMatrix = inMatrix;
                    Dtk_tab<Dtk_ID> outIds;
                    //Go Through the Root Node and children
                    WriteNode(rootNode, outIds);
                    if (outIds.size() == 1)
                    {
                        outPrototypeId = outIds[0];
                    }
                    else if (outIds.size() > 1)
                    {
                        Dtk_ID outIdass;
                        auto status = m_fileWriter->CreateAssembly(inComponent, outIdass);
                        Dtk_transfoPtr MatrixIdentity = Dtk_transfo::create();
                        for( Dtk_Size_t j = 0; j < outIds.size(); j++ )
                        {
                            Dtk_ID outIdInst;
                            status = m_fileWriter->CreateInstance( outIdass, outIds[ j ], MatrixIdentity, outIdInst );
                        }
                        outPrototypeId = outIdass;
                        m_fileWriter->CloseAssembly(inComponent);
                    }
    
                    if (m_fileWriter)
                    {
                        m_fileWriter->WriteGlobalDataSet(inComponent->GetGlobalDataSet());
        
                        Dtk_Size_t NumMetaData;
                        NumMetaData = inComponent->GetNumMetaData();
                        if (NumMetaData)
                        {
                            for (Dtk_Size_t i = 0; i < NumMetaData; i++)
                            {
                                m_fileWriter->WriteMetaData( inComponent->GetMetaData(i));
                            }
                        }
                    }
                }    
            }

            //We close the opened Component and free his construction tree
            inAPI->EndComponent(inComponent);
    
        }
        else
        {
            outPrototypeId = m_mpComponentProcessed[componentID];
        }
        return err;
    }
}
    